﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

namespace Zhongy
{
    public class BroadcastPer
    {
        private readonly string broadCastHost = "255.255.255.255";

        public Action<int> sendResultEvent = null;
        private byte[] sendBuff = null;

        private SocketAsyncEventArgs sendEventArgs = null;

        private Socket socket = null;
        private int buffersize = 2048;

        private string localHost = string.Empty;
        private int localPort = 0;

        public BroadcastPer()
        {
            sendBuff = new byte[buffersize];
            sendEventArgs = new SocketAsyncEventArgs();
            sendEventArgs.Completed += send_Completed;
            sendEventArgs.SetBuffer(sendBuff, 0, buffersize);
        }

        public void Start(string localhost, int localport)
        {
            //if (string.IsNullOrEmpty(localhost))
            //    Debug.LogError("localhost cannot be null");

            if (localport < 1 || localport > 65535)
                Debug.LogError("localport is out of range");

            this.localHost = localhost;
            this.localPort = localport;

            try
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);

                if (!string.IsNullOrEmpty(localHost))
                {
                    IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(localHost), localPort);
                    socket.Bind(endpoint);
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = (AsyncUserToken)e.UserToken;

            if (e.SocketError == SocketError.Success)
            {
                if (sendResultEvent != null)
                    sendResultEvent(e.BytesTransferred);
            }
            else
            {
                if (sendResultEvent != null)
                    sendResultEvent(e.BytesTransferred);

                Restart();
            }
        }
        internal class AsyncUserToken
        {
            public System.Net.Sockets.Socket Socket { get; set; }
        }

        private void send_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.SendTo:
                    ProcessSend(e);
                    break;
                default:
                    Debug.LogError("The last operation completed on the socket was not  send");
                    break;
            }
        }

        public void CloseSocket()
        {
            if (socket == null)
                return;

            try
            {
                socket.Shutdown(SocketShutdown.Both);
            }
            catch
            {
            }

            try
            {
                socket.Close();
            }
            catch
            {
            }
        }

        public void Restart()
        {
            CloseSocket();

            Start(localHost,localPort);
        }

        private Queue<byte[]> sendQueue = new Queue<byte[]>();
        bool isSendProcess = false;

        //public void Send(BroadMsg udpMsg)
        //{
        //    if (udpMsg == null)
        //        Debug.LogError("udpmsg is null");

        //    //byte[] data = EncodeTool.EncodeMsg(udpMsg);


        //    Send(data);
        //}

        public void Send(byte[] packet)
        {
            sendQueue.Enqueue(packet);
            if (!isSendProcess)
                send();
        }

        private void send()
        {
            isSendProcess = true;

            if (sendQueue.Count == 0)
            {
                isSendProcess = false;
                return;
            }
            byte[] packet = sendQueue.Dequeue();

            sendEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(broadCastHost), localPort);
            packet.CopyTo(sendEventArgs.Buffer, 0);
            sendEventArgs.SetBuffer( 0, packet.Length);
            
            bool result = socket.SendToAsync(sendEventArgs);
            if (result == false)
            {
                ProcessSend(sendEventArgs);
            }
        }

        private string lastMsg = string.Empty;
        public void SendMessageByBroadcast(string message)
        {
            if (socket == null)
                throw new ArgumentNullException("socket cannot be null");
            if (string.IsNullOrEmpty(message))
                throw new ArgumentNullException("message cannot be null");

#if DEBUG_BROAD
            if (!lastMsg.Equals(message))
            {

                Debug.Log("<color=red>广播：" + message + "</color>");
                lastMsg = message;
            }
#endif

            byte[] buff = Encoding.UTF8.GetBytes(message);
            if (buff.Length > buffersize)
                throw new ArgumentOutOfRangeException("message is out off range");

            sendEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(broadCastHost), localPort);
            buff.CopyTo(sendEventArgs.Buffer, 0);
            sendEventArgs.SetBuffer(0, buff.Length);
            bool willRaiseEvent = socket.SendToAsync(sendEventArgs);
            if (!willRaiseEvent)
            {
                ProcessSend(sendEventArgs);
            }
        }

    }
}
